package com.sihai.core.support;

import com.sihai.core.*;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.Collection;

/**
 * <code>HibernateDataAccessor</code> Hibernate implementation for {@link DataAccessor}
 */
public class HibernateDataAccessor extends HibernateDaoSupport implements DataAccessor {


    @Override
    public <T> T get(Class<T> type, Serializable id) {
        return type.cast(getHibernateTemplate().get(type, id));
    }

    @Override
    public <T> T load(Class<T> type, Serializable id) {
        return type.cast(getHibernateTemplate().load(type, id));
    }

    @Override
    public Serializable save(Object entity) {
        return getHibernateTemplate().save(entity);
    }

    @Override
    public void saveOrUpdate(Object entity) {
        getHibernateTemplate().saveOrUpdate(entity);
    }

    @Override
    public void saveOrUpdateAll(Collection<?> entities) {
        if (entities != null && !entities.isEmpty()) {
            getHibernateTemplate().saveOrUpdateAll(entities);
        }
    }

    @Override
    public void update(Object entity) {
        getHibernateTemplate().update(entity);
    }

    @Override
    public void delete(Object entity) {
        getHibernateTemplate().delete(entity);
    }

    @Override
    public void refresh(Object entity) {
        getHibernateTemplate().refresh(entity);
    }

    @Override
    public int batchUpdate(final QueryCreator creator) {

        return (Integer) getHibernateTemplate().execute(
                new HibernateCallback() {
                    @Override
                    public Object doInHibernate(Session session) throws HibernateException, SQLException {
                        return creator.createQuery(session).executeUpdate();
                    }
                });
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> LimitedList<T> findByCriteria(final Class<T> type, final CriteriaCreator creator, final Limit limit, final Sorter sorter) {

        return (LimitedList<T>) getHibernateTemplate().execute(new HibernateCallback() {

            @Override
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Criteria criteria = creator.createCriteria(session);
                if (sorter != null) {
                    sorter.decorate(criteria);
                }
                if (limit == null) {
                    return new LimitedList(criteria.list());
                }
                int totalcount = new CriteriaTotalCountGetter(session, criteria).count();
                criteria.setFirstResult(limit.getOffset()).setMaxResults(limit.getCount());
                return new LimitedList(totalcount, criteria.list());
            }
        });
    }


    @Override
    @SuppressWarnings("unchecked")
    public <T> LimitedList<T> findBySafeCriteria(Class<T> type, final CriteriaCreator creator, final Limit limit, final Sorter sorter) {
        return (LimitedList<T>) getHibernateTemplate().execute(new HibernateCallback() {

            @Override
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Criteria criteria = creator.createCriteria(session);
                if (sorter != null) {
                    sorter.decorate(criteria);
                }
                if (limit == null) {
                    return new LimitedList(criteria.list());
                }
                int totalcount = new ScrollableResultsTotalCountGetter(criteria.scroll()).count();
                criteria.setFirstResult(limit.getOffset()).setMaxResults(limit.getCount());
                return new LimitedList(totalcount, criteria.list());
            }
        });
    }


    protected QueryCreator createHqlBaseQueryCreator(final String hql, final Object[] values) {
        return new QueryCreator() {
            @Override
            public Query createQuery(Session session) {
                Query query = session.createQuery(hql);
                if ((values != null) && (values.length != 0)) {
                    for (int i = 0; i < values.length; i++) {
                        query.setParameter(i, values[i]);
                    }
                }
                return query;
            }
        };
    }


    @Override
    public <T> LimitedList<T> findByHql(Class<T> type, final String hql, final Object[] values, Limit limit, Sorter sorter) {
        return this.findByQuery(type, createHqlBaseQueryCreator(hql, values), limit, sorter);
    }


    @Override
    public <T> LimitedList<T> findBySafeHql(Class<T> type, final String hql, final Object[] values, Limit limit, Sorter sorter) {
        return this.findBySafeQuery(type, createHqlBaseQueryCreator(hql, values), limit, sorter);
    }

    @Override
    public <T> LimitedList<T> findByClass(final Class<T> type, Limit limit, Sorter sort) {
        return findByCriteria(type, new CriteriaCreator() {
            @Override
            public Criteria createCriteria(Session session) {
                return session.createCriteria(type);
            }
        }, limit, sort);
    }


    @SuppressWarnings("unchecked")
    protected LimitedList populateResultByQuery(Query query, Limit limit) {
        if (limit == null) {
            return new LimitedList(query.list());
        }
        TotalcountGetter totalcountGetter;
        if (query instanceof CountableQuery) {
            totalcountGetter = new QueryTotalCountGetter(CountableQuery.class.cast(query).getCountQuery());
        } else {
            totalcountGetter = new ScrollableResultsTotalCountGetter(query.scroll());
        }
        int totalcount = totalcountGetter.count();
        query.setFirstResult(limit.getOffset()).setMaxResults(limit.getCount());
        return new LimitedList(totalcount, query.list());
    }


    @SuppressWarnings("unchecked")
    @Override
    public <T> LimitedList<T> findByQuery(Class<T> type, final QueryCreator creator, final Limit limit, final Sorter sorter) {
        return (LimitedList<T>) getHibernateTemplate().execute(
                new HibernateCallback() {
                    @Override
                    public Object doInHibernate(Session session) throws HibernateException, SQLException {
                        Query query = creator.createQuery(new EnhancedSession(session, sorter));
                        if (limit == null) {
                            return new LimitedList(query.list());
                        }
                        int totalcount = new QueryTotalCountGetter(CountableQuery.class.cast(query).getCountQuery()).count();
                        query.setFirstResult(limit.getOffset()).setMaxResults(limit.getCount());
                        return new LimitedList(totalcount, query.list());
                    }
                });
    }


    @Override
    @SuppressWarnings("unchecked")
    public <T> LimitedList<T> findBySafeQuery(Class<T> type, final QueryCreator creator, final Limit limit, final Sorter sorter) {
        return (LimitedList<T>) getHibernateTemplate().execute(
                new HibernateCallback() {
                    @Override
                    public Object doInHibernate(Session session) throws HibernateException, SQLException {
                        Query query = creator.createQuery(new EnhancedSession(session, sorter));
                        if (limit == null) {
                            return new LimitedList(query.list());
                        }
                        int totalcount = new ScrollableResultsTotalCountGetter(query.scroll()).count();
                        query.setFirstResult(limit.getOffset()).setMaxResults(limit.getCount());
                        return new LimitedList(totalcount, query.list());
                    }
                });
    }


    @Override
    public <T> T findUnique(Class<T> type, final CriteriaCreator creator) {
        return type.cast(getHibernateTemplate().execute(
                new HibernateCallback() {
                    @Override
                    public Object doInHibernate(Session session) throws HibernateException, SQLException {
                        return creator.createCriteria(session).uniqueResult();
                    }
                }));
    }

    @Override
    public <T> T findUnique(Class<T> type, final QueryCreator creator) {
        return type.cast(getHibernateTemplate().execute(
                new HibernateCallback() {
                    @Override
                    public Object doInHibernate(Session session) throws HibernateException, SQLException {
                        return creator.createQuery(session).uniqueResult();
                    }
                }));
    }


}
